package com.codefish.framework.spring.core.utils;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * @author <a href='https://gitee.com/hzf2281'>codefish</a>
 * @version 1.0.0
 * @since 2022/09/23 下午 06:07
 */
public abstract class ClassUtils {

    private static final String BASE_DIR;
    private static final String JAVA_CLASS_EXTENSION = ".class";

    static {
        BASE_DIR = ClassLoader.getSystemResource("").getPath();
    }

    /**
     * 扫描指定包及其子包下所有的 *.class 文件，并加载
     *
     * @param basePackage
     * @return
     */
    public static List<Class<?>> scanClasses(String basePackage) {
        return scanClasses(basePackage, new File(BASE_DIR, transformClassPathToFilePath(basePackage)));
    }


    /**
     * 从传入的所有参数中按顺序获取对应的类
     *
     * @param objects 参数集
     * @return 参数集对应的类集
     */
    public static Class<?>[] getClassFromObjects(Object... objects) {
        if (objects == null || objects.length == 0) {
            return null;
        }
        Class<?>[] targetClasses = new Class[objects.length];
        int k = 0;
        for (Object object : objects) {
            targetClasses[k++] = object.getClass();
        }
        return targetClasses;
    }

    public static void setProperty(Object object, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {
        Field declaredField = null;
        Class<?> clazz = object.getClass();

        //从当前类开始，向父类搜索field是否存在
        while (clazz != null) {
            try {
                declaredField = clazz.getDeclaredField(fieldName);
                break;
            } catch (Exception e) {
                clazz = clazz.getSuperclass();
            }
        }

        if (declaredField == null) {
            throw new IllegalArgumentException(object.getClass().getName() + "及其父类不存在属性'" + fieldName + "'");
        }

        //尝试转换value为匹配的类型
        if (value instanceof String) {
            value = ConvertUtils.convert((String) value, declaredField.getType());
        }

        declaredField.setAccessible(true);
        declaredField.set(object, value);
    }

    /**
     * 判断某个class是否包含某个属性字段
     *
     * @param clazz     目标class
     * @param fieldName 目标属性字段
     * @return true表示包含，false表示不包含
     */
    public static boolean containField(Class<?> clazz, String fieldName) {
        if (clazz == null || StringUtils.isEmpty(fieldName)) {
            return false;
        }
        try {
            clazz.getDeclaredField(fieldName);
            return true;
        } catch (NoSuchFieldException e) {
            //ignore e
            return false;
        }
    }

    /**
     * 判断某个class是否包含某个属性字段，且该字段类型为fieldClass
     *
     * @param clazz      目标class
     * @param fieldName  目标属性字段
     * @param fieldClass 目标属性字段的class
     * @return true表示包含，false表示不包含
     */
    public static boolean containField(Class<?> clazz, String fieldName, Class<?> fieldClass) {
        if (clazz == null || StringUtils.isEmpty(fieldName) || fieldClass == null) {
            return false;
        }
        try {
            Field field = clazz.getDeclaredField(fieldName);
            return field.getType() == fieldClass;
        } catch (NoSuchFieldException e) {
            //ignore e
            return false;
        }
    }

    /**
     * 获取默认的classLoader
     *
     * @return 默认的classLoader
     */
    public static ClassLoader getDefaultClassLoader() {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        if (cl == null) {
            cl = ClassUtils.class.getClassLoader();
            if (cl == null) {
                cl = ClassLoader.getSystemClassLoader();
            }
        }
        return cl;
    }

    public static <T> T invokeMethod(Object object, String methodName, Class<T> returnType, Object... methodParams) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        AssertUtils.notNull(object, "object不能为null");
        AssertUtils.notNull(methodName, "methodName不能为null");
        Class<?> objectClass = object.getClass();
        Method method = objectClass.getMethod(methodName, getClassFromObjects(methodParams));
        method.setAccessible(true);
        Object returnObj = method.invoke(object, methodParams);
        if (returnObj == null || returnType.isAssignableFrom(returnObj.getClass())) {
            return (T) returnObj;
        } else {
            throw new IllegalArgumentException("方法的返回类型为'" + returnObj.getClass().getName() + "',不能转换为类型'" + returnType + "'");
        }
    }


    private static List<Class<?>> scanClasses(String basePackage, File file) {
        List<Class<?>> list = new ArrayList<>();
        if (file.isDirectory()) {
            //如果是文件夹，递归处理该文件夹下所有的*.class
            File[] subFiles = file.listFiles();
            if (subFiles != null) {
                for (File subFile : subFiles) {
                    String nextPackageName = subFile.isFile() ? getClassName(subFile.getName()) : subFile.getName();
                    list.addAll(scanClasses(basePackage + "." + nextPackageName, subFile));
                }
            }
        } else {
            if (file.getName().endsWith(JAVA_CLASS_EXTENSION)) {
                //判断文件是否以 *.class结尾,如果是，则加载class
                try {
                    Class<?> aClass = Class.forName(basePackage, false, getDefaultClassLoader());
                    list.add(aClass);
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
        }
        return list;
    }

    /**
     * 包路径转换为文件相对路径，例如 foo.bar.pig => foo\bar\pig
     *
     * @param classPath
     * @return
     */
    private static String transformClassPathToFilePath(String classPath) {
        return classPath.replaceAll("[.]", "\\\\");
    }

    /**
     * foo.class => foo
     *
     * @param classFileName
     * @return
     */
    private static String getClassName(String classFileName) {
        return classFileName.substring(0, classFileName.indexOf("."));
    }


}
